
/*
 * Copyright (C) ZHANG Xin
 * LICENSE: MIT
 */

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include <Python.h>
#include <stdlib.h>
#include <ngx_http_asgi_handler.h>

static ngx_int_t ngx_http_asgi_init(ngx_conf_t *cf);
static void *ngx_http_asgi_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_asgi_set_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_asgi_set_app(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static ngx_command_t ngx_http_asgi_commands[] = {
    {
        ngx_string("asgi_app_path"),
        NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1,
        ngx_http_asgi_set_path,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL },
   {
        ngx_string("asgi_app"),
        NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
        ngx_http_asgi_set_app,
        NGX_HTTP_LOC_CONF_OFFSET,
        0,
        NULL },

    ngx_null_command
};

static ngx_http_module_t ngx_http_asgi_module_ctx = {
    NULL,                          /* preconfiguration */
    ngx_http_asgi_init,            /* postconfiguration */
    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */
    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */
    ngx_http_asgi_create_loc_conf, /* create location configuration */
    NULL                           /* merge location configuration */
};

static void *
ngx_http_asgi_create_loc_conf(ngx_conf_t *cf) {
    ngx_http_asgi_conf_t *c = ngx_pcalloc(cf->pool, sizeof(ngx_http_asgi_conf_t));
    if (c == NULL) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "Init location config of ASGI module failed");
    }
    return c;
}

static ngx_int_t
ngx_http_asgi_init(ngx_conf_t *cf) {
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }
    *h = ngx_http_asgi_handler;
    return NGX_OK;
};

static char *
ngx_http_asgi_set_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_str_t *params = cf->args->elts;
    if (params[1].len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "'asgi_app_path' cannot be empty");
        return NGX_CONF_ERROR;
    }

    if (!Py_IsInitialized()) {
        if (cf != NULL) {
            u_char buf[1024];
            ngx_sprintf(buf, "%s:${PYTHONPATH}", params[1].data);
            setenv("PYTHONPATH", (char *)buf, 1);
        }
        Py_Initialize();
    }
    ngx_conf_log_error(NGX_LOG_NOTICE, cf, 0, "Set ASGI app path: %s", params[1].data);
    return NGX_CONF_OK;
}

static char *
ngx_http_asgi_set_app(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_str_t *params = cf->args->elts;
    if (params[1].len == 0 || params[2].len == 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "'asgi_app' doesn't take empty parameters");
        goto error;
    }
    PyObject *module, *app;
    module = PyImport_ImportModule((char *)params[1].data);
    if (module == NULL) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "connot find module: %s", params[1].data);
        goto module_error;
    }
    app = PyObject_GetAttrString(module, (char *)params[2].data);
    if (app == NULL) {
        Py_XDECREF(app);
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "connot find ASGI app: %s in module: %s", params[2].data, params[1].data);
        goto app_error;
    }
    if (!PyCallable_Check(app)) {
        Py_XDECREF(app);
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "ASGI app: %s in module: %s is not callable", params[2].data, params[1].data);
        goto app_error;
    }

    ngx_http_asgi_conf_t* c = conf;
    c->asgi_app = app;
    
    Py_DECREF(module);
    return NGX_CONF_OK;

    app_error:
    Py_DECREF(module);

    module_error:
    error:
    return NGX_CONF_ERROR;
}

ngx_module_t ngx_http_asgi_module = {
    NGX_MODULE_V1,
    &ngx_http_asgi_module_ctx,     /* module context */
    ngx_http_asgi_commands,        /* module directives */
    NGX_HTTP_MODULE,               /* module type */
    NULL,                          /* init master */
    NULL,                          /* init module */
    NULL,                          /* init process */
    NULL,                          /* init thread */
    NULL,                          /* exit thread */
    NULL,                          /* exit process */
    NULL,                          /* exit master */
    NGX_MODULE_V1_PADDING
};
